<?php

/**
 * \file
 * Classe `Collection`.
 *
 * \since 08/10/2008
 * \author Matthieu OVIEDO <matthieu.oviedo@gmail.com>
 */

/**
 * Collections d'objets d'entités de données.
 */ 
namespace pilar\ObjectCollections /* \cond */ ; /* \endcond */
{

/**
 * Collection d'objets d'une entité de données.
 *
 * \todo
 * Etant donné qu'on exporte tous les champs d'une jointure exportée il
 * faudrait voir si il n'est pas plus judicieux de désactiver le
 * chargement dynamique des membres et des champs de jointures pour les 
 * remplacer par un chargement manuel à la construction de la jointure. 
 * A méditer...
 *
 * \since 08/10/2008
 * \author Matthieu OVIEDO <matthieu.oviedo@gmail.com>
 */
class Collection implements \Iterator {

/**
 * Entité de sélection.
 *
 * \since 08/10/2008
 */
private $_entity;


/**
 * Spécifie que la requête a déjà été exécutée, ou pas.
 *
 * \since 08/10/2008
 */
private $_executed = false;


/**
 * Compteur d'identifiants de jointure.
 *
 * \since 08/10/2008
 */
private $_joinAliasCount = 0;



/**
 * Nombre de lignes à traiter à partir de la limite inférieure de
 * lecture.
 *
 * \since 08/10/2008
 */
private $_limitLength = null;


/**
 * Limite inférieure de lecture à utiliser pour la sélection.
 *
 * \since 08/10/2008
 */
private $_limitOffset = 0;



/**
 * Jointure principale.
 *
 * \since 08/10/2008
 */
private $_mainJoin;


/**
 * \since 08/10/2008
 *
 * Expressions de la clause ORDER BY de la requête SQL.
 */
private $_orders = array();


/**
 * Jointures.
 *
 * \since 08/10/2008
 */
private $_otherJoins = array();


/**
 * Résultat de la requête en base de données.
 *
 * \todo
 * Passer en privé !
 *
 * \since 08/10/2008
 */
public $_result;


/**
 * Expressions de la clause WHERE de la sélection.
 *
 * \since 08/10/2008
 */
private $_whereExp = null;


/**
 * Constructeur.
 * 
 * \param $entity Entité de sélection.
 *
 * \since 08/10/2008
 */
public function __construct($entity) {
 $this->_entity = $entity;
 
 // La jointure principale porte sur l'entité de sélection :
  $this->_mainJoin = new Join(
   $this,
   $this->_entity,
   '_',
   null,
   null
  );
  // Évidemment cette jointure est automatiquement exportée :
   $this->_mainJoin->export();
}


/**
 * Accède à un membre de la jointure principale.
 *
 * \param $memberName Nom du membre.
 * \return Membre de la jointure principale.
 *
 * \since 08/10/2008
 */
public function __get($memberName) {
 return $this->member($memberName);
}


/**
 * Accède à un membre de la jointure principale.
 *
 * \param $name Nom du membre.
 * \return Membre de la jointure principale.
 *
 * \since 16/03/2009
 * \author Matthieu OVIEDO <matthieu.oviedo@gmail.com> 
 */
public function member($name) {
 return $this->_mainJoin->__get($name);
}


/**
 * Inscrit une jointure.
 *
 * \since 08/10/2008
 */
public function addJoin($join) {
 $this->_otherJoins[] = $join;
}


/**
 * Génère un alias de jointure unique et le réserve.
 *
 * \return Alias de jointure généré.
 *
 * \since 08/10/2008
 */
public function bookJoinAlias() {
 /* TODO
  * Améliorer en fonction d'une lettre passée en paramètre pour gagner
  * en significativité.
  */

 $alias = 'J'.$this->_joinAliasCount;
 $this->_joinAliasCount++;
 
 return $alias;
}


/**
 * Accède à l'objet courant.
 *
 * \since 08/10/2008
 * \return Objet courant.
 */
public function current() {
 return $this->_mainJoin->currentObject();
}


/**
 * Supprime de la base de données les objets de la collection.
 *
 * \since 08/10/2008
 */
public function delete() {
 foreach($this as $o) $o->delete();
}


/**
 * Version DOM de la collection.
 *
 * \param DOMDocument parent.
 * \return DOMNode de la collection.
 *
 * \since 11/12/2008
 */
public function domNode($domDoc) {
 $domCollection = $domDoc->createElement($this->_entity->get('plName'));

 foreach($this as $object) {
  $domCollection->appendChild($object->domNode($domDoc));
 }

 return $domCollection; 
}


/**
 * Exécute la requête SELECT associée à la collection.
 *
 * \since 08/10/2008
 */
private function execute() {
 $this->_result = \db\query($this->sqlCode());

 // Marque la sélection comme effectuée :
  $this->_executed = true;
}


/**
 * Sélectionne les jointures de la sélection à exporter.
 *
 * \since 08/10/2008
 * \param $refs Structure en arbre des jointures à exporter.
 * \return $this.
 */
public function export($refs) {
 $this->_mainJoin->export($refs);

 return $this;
}


/**
 * Détermine si il existe des résultats.
 *
 * \return \c TRUE si il existe des résultats, \c FALSE sinon.
 *
 * \since 06/02/2009
 */
public function isEmpty() {
 if(!$this->_executed) $this->execute();

 return $this->_result->isEmpty();
}


/**
 */
public function key() {}


/**
 * Limite le nombre d'objets sélectionnés.
 *
 * \param $length Nombre d'objets à sélectionner. \c NULL pour une
 * infinité.
 * \param $offset Le nombre d'objets qu'on laisse tomber à partir du
 * début.
 * \return $this.
 *
 * \since 28/09/2008
 */
public function limit($length, $offset = 0) {
 $this->_limitOffset = $offset;
 $this->_limitLength = $length;

 return $this;
}


/**
 * Ajoute un ordre à la sélection.
 * 
 * \param $what Expression à utiliser pour ordonner les objets.
 * \param $how Ordre de classement ('asc' ou 'desc').
 * Par défaut 'asc'. 'asc' classe par ordre croissant et 'desc'
 * par ordre décroissant.
 * \return $this.
 *
 * \since 08/10/2008
 */
public function orderBy($what, $how = 'asc') {
 $this->_orders = new \pilar\Entities\expressions\order($what, $how);

 return $this; 
}


/**
 * Lit/extrait la prochaine ligne du résultat.
 *
 * \since 08/10/2008
 */
public function next() {
 // On extrait l'objet suivant si il existe :
  $this->_result->fetchArray();
}


/**
 * Initialise la lecture de la collection.
 *
 * \since 08/10/2008
 */
public function rewind() {
 if(!$this->_executed || $this->_result->row > 1) {
  $this->execute();
  $this->next(); // On débute la lecture.
 } else if($this->_result->row == 0) {
  $this->next();
 }
}


/**
 * Change les valeurs de certains champs.
 *
 * \todo
 * Voir si cette fonctionnalité est vraiment utile.
 *
 * \since 08/10/2008
 * \param $values Arbre de valeurs.
 * \return $this.
 */
public function set($values) {
 $this->_mainJoin->set($values);
}


/**
 * Commande SQL SELECT associée à la collection.
 *
 * \since 08/10/2008
 * \return Commande SELECT.
 */
public function sqlCode() {
 $idx = 0;
 
 $joins = array_merge(
  $this->_otherJoins,
  array($this->_mainJoin)
 );

 $query = 'SELECT ';
 foreach($joins as $join) {
  if($join->isExported()) {
   foreach($join->_joinedEntity->members
           as $memberName => $entityMember) {
    $member = $join->__get($memberName);
    foreach($entityMember->fields as $fieldName => $entityField) {
     $field = $member->field($fieldName);
     $field->setSqlIdx($idx); /* Distribution des indexes numériques
                                 de colonne. */
     if($idx > 0) $query .= ', ';
     $query .= $field->sqlCode();
     $idx++;
    }
   }
  }
 }
 $query .= ' FROM `'
           .$this->_mainJoin->_joinedEntity->sqlTableName().'` '
           .'AS '.$this->_mainJoin->alias();
 foreach($this->_otherJoins as $join) {
  if($join->needed()) {
   $query .= ' '.$join->sqlJoinCode();
  }
 }
 if($this->_whereExp != null) {
  $query .= ' WHERE '.$this->_whereExp->sqlCode();
 }
 if(!empty($this->_orders)) {
  $query .= ' ORDER BY ';
  $idx = 0;
  foreach($this->_orders as $order) {
   if($idx > 0) $query .= ', ';
   $query .= $order->sqlCode();
  }
 }
 if($this->_limitOffset != 0 || $this->_limitLength != null) {
  $nums = array();
  if($this->_limitOffset != 0) $nums[] = $this->_limitOffset;
  if($this->_limitLength != null) $nums[] = $this->_limitLength;
  $query .= ' LIMIT '.implode(',', $nums);
 }
 
 return $query;
}


/**
 * Sauvegarde en base de données les modifications effectuées sur les
 * objets de la collection.
 *
 * \todo
 * Voir si cette fonctionnalité est vraiment utile. Et si c'est le cas l'implémenter.
 *
 * \since 08/10/2008
 */
public function update() {
}


/**
 * Regarde si il y a encore des résultats.
 *
 * \since 08/10/2008
 * \return TRUE/FALSE.
 */
public function valid() {
 return ($this->_result->record != false);
}


/**
 * Spécifie quels seront les objets sélectionnés.
 *
 * \param $exp Expression à utiliser pour la clause WHERE.
 * \return $this.
 *
 * \since 28/09/2008
 */
public function where($exp) {
 $this->_whereExp = $exp;

 return $this; 
}


} // Class.

} // Namespace.

?>
